
/*
 * Copyright (C) 2010 Bitglue. All rights reserved.
 *
 * $Id: EclipseWarDeployer.java bself $
 */

/*
 * Copyright 2010-2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.osgi.web.deployer.eclipse;

import java.io.File;
import java.io.IOException;
import java.net.URLClassLoader;

import org.apache.tomcat.InstanceManager;
import org.apache.tomcat.JarScanner;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.WebAppContext;

import org.osgi.framework.Bundle;

import org.springframework.osgi.util.BundleDelegatingClassLoader;
import org.springframework.osgi.util.OsgiBundleUtils;
import org.springframework.osgi.util.OsgiStringUtils;
import org.springframework.osgi.web.deployer.OsgiWarDeploymentException;
import org.springframework.osgi.web.deployer.WarDeployment;
import org.springframework.osgi.web.deployer.WarDeploymentContext;
import org.springframework.osgi.web.deployer.internal.util.JasperUtils;
import org.springframework.osgi.web.deployer.internal.util.Utils;
import org.springframework.osgi.web.deployer.support.AbstractWarDeployer;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
 * EclipseWarDeployer deploys eclipse war bundles. This source was
 * ripped from Spring Dymamic Modules Jetty Web Deployer version and
 * modded to support eclipse 8.x.x as well as Tomcat's Jasper 7.
 * @author Costin Leau
 * @author Bryan Patrick Self
 */
public class EclipseWarDeployer extends AbstractWarDeployer {

    // these are loaded by the war parent class-loader
    private static final String[] SYSTEMCLASSES = {
      "java.", "javax.servlet.", "javax.xml.", "org.eclipse.",
    };

    // these aren't loaded by the war class-loader
    private static final String[] SERVERCLASSES = {
      "-org.eclipse.jetty.plus.jaas.", "org.eclipse.jetty.",
    };

    private Server serverService;

    public void setServer(Object server) {
        if (server != null) {
            Assert.isInstanceOf(Server.class, server,
              "Invalid Eclipse Server given:");
            this.serverService = (Server)server;
        }
    }

    public void afterPropertiesSet() throws Exception {
        super.afterPropertiesSet();
        if (serverService == null) {
            log.info("No Eclipse Server set; "
              + "looking for one in the OSGi service registry...");
            try {
                serverService = (Server)Utils.createServerServiceProxy(
                  getBundleContext(), Server.class, null);
                log.info("Found service " + serverService);
            } catch (RuntimeException ex) {
                log.error("No Eclipse Server found, bailing out", ex);
                throw ex;
            }
        }
    }

    protected WarDeployment createDeployment(Bundle bundle,
      String contextPath) throws Exception {
        WebAppContext wac = createEclipseWebContext(bundle, contextPath);
        wac.setAttribute(WarDeploymentContext.OSGI_BUNDLE_CONTEXT_ATTRIBUTE,
          OsgiBundleUtils.getBundleContext(bundle));
        EclipseWarDeployment deployment = new EclipseWarDeployment(
          new EclipseContextUndeployer() {
            public void undeploy(WebAppContext webAppCtx)
              throws OsgiWarDeploymentException {
                stopWebAppContext(webAppCtx);
            }
        }, bundle, wac);
        return deployment;
    }

    protected void startDeployment(WarDeployment deployment)
      throws Exception {
        Assert.isInstanceOf(EclipseWarDeployment.class,
          deployment, "Wrong type of deployment used");
        startWebAppContext(((EclipseWarDeployment)deployment)
          .getWebAppContext());
    }

    protected WebAppContext createEclipseWebContext(Bundle bundle,
      String contextPath) throws Exception {
        WebAppContext wac = new WebAppContext();
        wac.setServer(serverService);
        wac.setWar(OsgiStringUtils.nullSafeName(bundle));
        wac.setContextPath(contextPath.replaceAll(".[^-]+$", ""));
        wac.setTempDirectory(new File("/tmp"));
        wac.setCopyWebDir(false);
        wac.setExtractWAR(true);

        Resource rootResource = getRootResource(bundle, wac);
        wac.setBaseResource(rootResource);
        wac.setWar(null);

        wac.setSystemClasses(SYSTEMCLASSES);
        wac.setServerClasses(SERVERCLASSES);
        wac.setParentLoaderPriority(false);
        wac.setClassLoader(createWebAppClassLoader(bundle, Server.class));
        wac.getServletContext().setAttribute(InstanceManager.class.getName(),
          new EclipseInstanceManager());
        wac.getServletContext().setAttribute(JarScanner.class.getName(),
          new EclipseJarScanner());
        return wac;
    }

    protected URLClassLoader createWebAppClassLoader(Bundle bundle,
      Class serverClass) {
        ClassLoader serverClassLoader
          = Utils.chainedWebClassLoaders(serverClass);
        ClassLoader extraClassLoader = serverClassLoader;
        String wac = "org.eclipse.jetty.webapp.WebAppContext";
        try {
            Class clazz = ClassUtils.forName(wac, Utils.class.getClassLoader());
            extraClassLoader = clazz.getClassLoader();
        } catch (Exception ex) {
            log.error("can't find class loader for " + wac, ex);
        }
        ClassLoader classLoader = BundleDelegatingClassLoader
          .createBundleClassLoaderFor(bundle, extraClassLoader);
        URLClassLoader urlClassLoader = JasperUtils.createJasperClassLoader(
          bundle, classLoader);
        return urlClassLoader;
    }

    private Resource getRootResource(Bundle bundle, WebAppContext wac)
      throws Exception {
        File unpackFolder = unpackBundle(bundle, wac);
        return Resource.newResource(unpackFolder.getCanonicalPath());
    }

    private void startWebAppContext(WebAppContext wac) throws Exception {
        HandlerCollection contexts = getEclipseContexts();
        Thread current = Thread.currentThread();
        ClassLoader old = current.getContextClassLoader();
        try {
            current.setContextClassLoader(wac.getClassLoader());
            if (contexts != null)
                contexts.addHandler(wac);
            wac.start();
            if (contexts != null)
                contexts.start();
        } finally {
            current.setContextClassLoader(old);
        }
    }

    private void stopWebAppContext(WebAppContext wac)
      throws OsgiWarDeploymentException {
        Resource rootResource = wac.getBaseResource();
        String contextPath = wac.getContextPath();
        String messageEnding = "context [" + contextPath
          + "] from server " + getServerInfo();
        log.info("About to undeploy " + messageEnding);

        HandlerCollection contexts = getEclipseContexts();
        Thread current = Thread.currentThread();
        ClassLoader old = current.getContextClassLoader();
        try {
            current.setContextClassLoader(wac.getClassLoader());
            wac.stop();
            if (contexts != null)
                contexts.removeHandler(wac);
            log.info("Context [" + contextPath
              + "] undeployed successfully from server "
              + getServerInfo());
        } catch (Exception ex) {
            throw new OsgiWarDeploymentException("Cannot undeploy "
              + messageEnding, ex);
        } finally {
            current.setContextClassLoader(old);
            if (log.isDebugEnabled())
                log.debug("Cleaning unpacked folder " + rootResource);
            try {
                IO.delete(rootResource.getFile());
            } catch (IOException ex) {
                log.warn("Could not clean unpacked folder for "
                  + messageEnding, ex);
            }
        }
    }

    private HandlerCollection getEclipseContexts() {
        HandlerCollection contexts = (HandlerCollection)serverService
          .getChildHandlerByClass(ContextHandlerCollection.class);
        if (contexts == null) {
            contexts = (HandlerCollection)serverService
              .getChildHandlerByClass(HandlerCollection.class);
        }
        return contexts;
    }

    private File unpackBundle(Bundle bundle, WebAppContext wac)
      throws Exception {
        File tmpFile = new File(wac.getTempDirectory()
          + wac.getContextPath() + ".osgi");
        tmpFile.delete();
        tmpFile.mkdir();
        if (log.isDebugEnabled()) {
            log.debug("Unpacking bundle "
              + OsgiStringUtils.nullSafeNameAndSymName(bundle)
              + " to folder [" + tmpFile.getCanonicalPath() + "] ...");
        }
        Utils.unpackBundle(bundle, tmpFile);
        tmpFile.deleteOnExit();
        return tmpFile;
    }

    protected String getServerInfo() {
        return "Eclipse-" + Server.getVersion();
    }

}
